package com.hmdp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hmdp.dto.Result;
import com.hmdp.dto.ScrollResult;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.Blog;
import com.hmdp.entity.Follow;
import com.hmdp.entity.User;
import com.hmdp.mapper.BlogMapper;
import com.hmdp.mapper.FollowMapper;
import com.hmdp.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.service.IFollowService;
import com.hmdp.service.IUserService;
import com.hmdp.utils.SystemConstants;
import com.hmdp.utils.UserHolder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import static com.hmdp.utils.RedisConstants.BLOG_LIKED_KEY;
import static com.hmdp.utils.RedisConstants.FEED_KEY;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {
    @Autowired
    private IUserService userService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private FollowMapper followMapper;

    @Override
    public Result queryHotBlog(Integer current) {
        // 根据用户查询
        Page<Blog> page = this.query()
                .orderByDesc("liked")
                .page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        // 查询用户
        records.forEach(blog -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
        });
        return Result.ok(records);
    }

    @Override
    public Result queryBlogById(Long id) {
        // 1.查询blog
        Blog blog = this.getById(id);
        if (blog == null) {
            return Result.fail("笔记不存在");
        }
        //2.查询与blog有关的用户信息
        queryBlogUser(blog);
        //3.判断当前用户是否点赞
        isBlogLiked(blog);
        return Result.ok(blog);
    }

    private void isBlogLiked(Blog blog) {
        UserDTO user = UserHolder.getUser();
        //若用户未登陆，则不查询是否已点赞
        if (user == null) {
            return;
        }
        Long userId = user.getId();
        Double isLike = stringRedisTemplate.opsForZSet().score(BLOG_LIKED_KEY + blog.getId(), userId.toString());
        blog.setIsLike(isLike != null);
    }

    @Override
    public Result likeBlog(Long id) {
        Long userId = UserHolder.getUser().getId();
        //1.通过查询用户id是否在指定的blog的set集合中，来判断是否已点赞
        Double isExist = stringRedisTemplate.opsForZSet().score(BLOG_LIKED_KEY + id, userId.toString());
        if (isExist != null) {
            //2.若存在，则说明已点赞 此时点赞数-1 并将用户id移出set集合中
            boolean isSuccess = this.update().setSql("liked = liked - 1").eq("id", id).update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().remove(BLOG_LIKED_KEY + id,userId.toString());
            }
        }else {
            //3.若不存在，则说明未点赞 此时点赞数+1 并将用户id加入set集合中
            boolean isSuccess = this.update().setSql("liked = liked + 1").eq("id", id).update();
            if (isSuccess) {
                stringRedisTemplate.opsForZSet().add(BLOG_LIKED_KEY + id,userId.toString(),System.currentTimeMillis());
            }
        }
        return Result.ok();
    }

    @Override
    public Result queryLikes(Long id) {
        List<UserDTO> userDTOList = new ArrayList<>();

        //1.根据blogId获取给该文章点赞的用户id
        Set<String> userIds = stringRedisTemplate.opsForZSet().range(BLOG_LIKED_KEY + id, 0, 4);

        //2.根据用户id获取用户信息
        for (String userId : userIds) {
            User user = userService.getById(userId);
            UserDTO userDTO = new UserDTO();
            BeanUtils.copyProperties(user, userDTO);
            userDTOList.add(userDTO);
        }

        return Result.ok(userDTOList);
    }

    @Override
    public Result saveBlog(Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 保存探店博文
        boolean isSuccess = this.save(blog);
        if (!isSuccess) {
            return Result.fail("保存探店博文失败！");
        }
        //获取当前用户的所有粉丝，推送当前博文到粉丝的邮件中
        LambdaQueryWrapper<Follow> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Follow::getFollowUserId, user.getId());
        List<Follow> follows = followMapper.selectList(lqw);
        for (Follow follow : follows) {
            String key = FEED_KEY + follow.getUserId();
            stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());
        }

        // 返回id
        return Result.ok(blog.getId());
    }

    /**
        首次查询：RevRangeByScore myset 1000 0 WITHSCORES 0 2
        后续查询：RevRangeByScore myset max 0 WITHSCORES offset 2
        其中：第一次查询   max是当前时间戳   offset默认为0
            第二次查询开始 max是上一次查询的最小时间戳  offset是上次查询得出的最小时间戳的重复个数,即本次查询的偏移量
     */
    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        //1.获取当前登陆用户的key
        Long id = UserHolder.getUser().getId();
        String key = FEED_KEY + id;
        //2.从用户的收件箱中拉取信息
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,0,max, offset, 2);
        if (typedTuples == null || typedTuples.size() == 0) {
            return Result.ok();
        }
        //3.遍历获取关注的博文信息，并获取本次查询的最小时间戳 和 偏移量
        long minTime = 0;//最小时间戳
        int os = 1;//偏移量
        List<Blog> blogList = new ArrayList<>(typedTuples.size());
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            //获取博文信息
            String blogId = typedTuple.getValue();//博文Id
            long blogTime = typedTuple.getScore().longValue();//博文时间戳
            Blog blog = this.getById(blogId);
            //查询与blog有关的用户信息
            queryBlogUser(blog);
            //判断当前用户是否点赞
            isBlogLiked(blog);
            blogList.add(blog);

            //获取本次查询的最小时间戳 以及 偏移量
            if (minTime == blogTime){
                os++;//发现有重复的时间戳，增加偏移量
            }else {
                minTime = blogTime;
                os = 1;//若发现更小的时间戳 则重置偏移量
            }
        }

        //如果本次最小时间戳还是和上次的一样，则偏移量需要叠加起来
        /*
             例如：5 5 5 5 5 6每次读取2
             则：第一次 读取5 5 计算得出max=5 offset=2
                第二次 读取5 5 计算得出max=5 offset=2+2=4
                第三次 读取5 6 计算得出max=5 offset=4+1=5
             本质上offset就是用来跳过之前重复的最小分数的值，如果存在多次查询的值的最小分数都一样，说明要跳过这几个值，则他们得出的偏移量要累加起来
         */
        os = minTime == max ? os + offset : os;

        //4.响应结果
        ScrollResult result = new ScrollResult();
        result.setList(blogList);
        result.setMinTime(minTime);
        result.setOffset(os);
        return Result.ok(result);
    }

    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userService.getById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }
}
